from Board import Board
from BoardCell import BoardCell
from time import *
import random
import csp
from CellCSPVariable import *

class BoardCSP(csp.CSP):
    """Defines a CSP for a particular board setup."""
    def __init__(self,board,mcv = True, lcv = False, fc=False, mac = False):
        self.board = board
        self.solutions = []
        self.mcv = mcv
        self.lcv = lcv
        self.fc = fc
        self.board.clearCSPData()
        self.solutions = []
        self.vars = []
        self.currentFringe = 0
        self.checkBoard(self.board)
        self.fringes = self.findFringes()
        self.fringes.sort(lambda x,y: len(x) < len(y))
        self.currentFringe = 0


    def nextFringe(self):
        if self.currentFringe >= len(self.fringes):
            return False
        fringe = self.fringes[self.currentFringe]
        self.currentFringe += 1
        variables = [CellCSPVariable(c) for c in [self.board[k] for k in self.findConstraints(fringe)]]
        domains = self.findDomains(variables)
        neighbors = self.findNeighbors(variables)
        csp.CSP.__init__(self,variables,domains,neighbors,self.compareVariables)
        return True

    def checkBoard(self,board):
        for row in board:
            for col in row:
                assert(col,BoardCell)

    def checkVariables(self,variables):
        for var in variables:
            assert(isinstance(var,CellCSPVariable))

    def findFringes(self):
        """Find and return the different sub problems fringes. The fringes are
        returned in an array, where each element of the array contains an array
        of coordinates for that particular fringe."""
        # Find the fringes to stuff into the csp
        w = self.board.width
        h = self.board.height
        groups = []
        for r in xrange(h):
            for c in xrange(w):
                # Square must be untouched
                if self.board[(r,c)].value != BoardCell.Unknown:
                    continue
                # Square must have numbered neighbours
                ourNeighbours = self.board.neighbours((r,c),1)
                match = False
                for (x,y) in ourNeighbours:
                    if self.board[x,y].value > 0:
                        match = True
                        break
                if not match:
                    continue
                # Group adjacent untouched squares together to form fringes
                match = False
                for group in groups:
                    for item in group:
                        neighbours = self.board.neighbours(item,2) 
                        if (r,c) in neighbours:
                            group.append((r,c))
                            match = True
                            break;
                if not match:
                    groups.append([(r,c)])
        # Group together groups that have one or more neighbouring numbers in common
        return groups

    def findConstraints(self,fringe):
        numbers = []
        for coord in fringe:
            neighbours = self.board.neighbours(coord,1)
            for neighbour in neighbours:
                if neighbour not in numbers and self.board[neighbour].value > 0:
                    numbers.append(neighbour)
        self.linkConstraints(numbers)
        return numbers

    def linkConstraints(self,constraints):
        for coord in constraints:
            neighbours = self.board.neighbours(coord,1)
            for neighbour in neighbours:
                if self.board[neighbour].value == -2:
                    self.board[coord].pointAtUnknown(self.board[neighbour])

    def compareVariables(self,A,a,B,b):
        iA = 0
        for memberA in A.members:
            iB = 0
            for memberB in B.members:
                if memberA.coordinate == memberB.coordinate:
                    if not a[iA] == b[iB]:
                        return False
                iB+=1
            iA+=1
        return True
        
    def findNeighbors(self,variables):
        neighbors = {}
        for var in variables:
            neighbors[var] = []
            for var2 in variables: 
                if not var2 == var:
                    for member in var2.members:
                        if member in var.members:
                            neighbors[var].append(var2)
                            break;
        return neighbors

    def findDomains(self,variables):
        domains = {}
        for var in variables:
            domains[var] = var.domain()
        return domains
